package fnord.jovian.tango;

import java.awt.*;
import java.util.Vector;

import fnord.jovian.*;

import fnord.jovian.tango.TheCard.TheCardOrientation;

public class TheCardTree implements TheObject {

	private TheCard _root;
	private Vector<TheCard> _cards;
	private Vector<ThePointWrapper> _outPoints;
	private TheCard _dragged;
	private ThePointWrapper _minPoint;
	private TheBoard _board;
	private double _rotation;
	private boolean _active;

	public TheCardTree(TheCard card, TheBoard board, boolean active) {
		_root = card;
		_cards = new Vector<TheCard>();
		_cards.add(_root);
		_root.setLocation((board.getWidth() - _root.getWidth())/2.0,
				(board.getHeight() - _root.getHeight())/2.0);
		_board = board;
		_active = active;
	}

	public int getTransferable() {
		int t = 0;
		for (int i = 0; i < _cards.size(); i++) {
			t += _cards.get(i).getCard().getTransferablePower();
		}
		return t;
	}

	@Override
	public void paint(Graphics2D g) {
		if (_active) {
			g.rotate(_rotation,_board.getX() + _board.getWidth()/2.0,_board.getY() - _board.getHeight()/2.0);

			for (int i = 0; i < _cards.size(); i++) {
				if (!_cards.get(i).isDragged())
					_cards.get(i).paint(g);
			}
			for (int i = 0; i < _cards.size(); i++) {
				if (_cards.get(i).isDragged())
					_cards.get(i).paint(g);
			}
			for (int i = 0; i < _cards.size(); i++) {
				_cards.get(i).paintTooltip(g);
			}
			g.rotate(-_rotation,_board.getX() + _board.getWidth()/2.0,_board.getY() - _board.getHeight()/2.0);
		}
	}

	public TheCard root() {
		return _root;
	}

	@Override
	public boolean contains(Point p) {
		if (_active) {
			boolean contains = false;
			for (int i = 0; i < _cards.size(); i++) {
				if(_cards.get(i).contains(p)) {
					_cards.get(i).containAction();
					contains = true;
				}
				else
					_cards.get(i).outAction();
			}
			return contains;
		}
		else
			return false;
	}
	
	public void addChild(TheCard parent, TheCard child, TheCardOrientation side) {
		if (_cards.contains(parent)) {
			parent.addChild(child, side);
			child.setParent(parent);
			_cards.add(child);
			reOrient(parent,child,side);
		}
		else {
			// PRINT ERROR
		}
	}

	private void reOrient(TheCard parent, TheCard child, TheCardOrientation side) {
		int orientation = (child.child(side) + child.child(parent.getOrientation())) % 4;
		child.setOrientation(child.childOrientation((orientation + 10 - child.child(child.inSide()))%4));
		switch(orientation) {
		case 0:
			if (child.child(parent.getOrientation()) == 0 ||
					child.child(parent.getOrientation()) == 2)
				child.setLocation(parent.getCenterX() - child.getWidth()/2.0,
						parent.getCenterY() + (parent.getWidth() + child.getWidth() - child.getHeight())/2.0);
			else
				child.setLocation(parent.getCenterX() - child.getWidth()/2.0,
						parent.getCenterY() + parent.getWidth()/2.0);
			break;
		case 1:
			if (child.child(parent.getOrientation()) == 0 ||
					child.child(parent.getOrientation()) == 2)
				child.setLocation(parent.getCenterX() + parent.getHeight()/2.0,
						parent.getCenterY() - child.getHeight()/2.0);
			else
				child.setLocation(parent.getCenterX() + (parent.getHeight() + child.getWidth() - child.getHeight())/2.0,
						parent.getCenterY() - child.getHeight()/2.0);
			break;
		case 2:
			if (child.child(parent.getOrientation()) == 0 ||
					child.child(parent.getOrientation()) == 2)
				child.setLocation(parent.getCenterX() - child.getWidth()/2.0,
						parent.getCenterY() - (parent.getWidth() + child.getWidth() - child.getHeight())/2.0 - child.getHeight());
			else
				child.setLocation(parent.getCenterX() - child.getWidth()/2.0,
						parent.getCenterY() - parent.getWidth()/2.0 - child.getHeight());
			break;
		case 3:
			if (child.child(parent.getOrientation()) == 0 ||
					child.child(parent.getOrientation()) == 2)
				child.setLocation(parent.getCenterX() - parent.getHeight()/2.0 - child.getWidth(),
						parent.getCenterY() - child.getHeight()/2.0);
			else
				child.setLocation(parent.getCenterX() - (parent.getHeight() + child.getWidth() - child.getHeight())/2.0 - child.getWidth(),
						parent.getCenterY() - child.getHeight()/2.0);
			break;
		default:
			System.err.println("Error!");
		}
	}

	private void reOrientTemp(TheCard parent, TheCard child, TheCardOrientation side) {
		int orientation = (child.child(side) + child.child(parent.getOrientation())) % 4;
		child.reOrientOrientation(child.childOrientation((orientation + 6 - child.child(child.inSide()))%4));
		switch(orientation) {
		case 0:
			if (child.child(parent.getOrientation()) == 0 ||
					child.child(parent.getOrientation()) == 2)
				child.reOrientLocation(parent.getCenterX() - child.getWidth()/2.0,
						parent.getCenterY() + (parent.getWidth() + child.getWidth() - child.getHeight())/2.0);
			else
				child.reOrientLocation(parent.getCenterX() - child.getWidth()/2.0,
						parent.getCenterY() + parent.getWidth()/2.0);
			break;
		case 1:
			if (child.child(parent.getOrientation()) == 0 ||
					child.child(parent.getOrientation()) == 2)
				child.reOrientLocation(parent.getCenterX() + parent.getHeight()/2.0,
						parent.getCenterY() - child.getHeight()/2.0);
			else
				child.reOrientLocation(parent.getCenterX() + (parent.getHeight() + child.getWidth() - child.getHeight())/2.0,
						parent.getCenterY() - child.getHeight()/2.0);
			break;
		case 2:
			if (child.child(parent.getOrientation()) == 0 ||
					child.child(parent.getOrientation()) == 2)
				child.reOrientLocation(parent.getCenterX() - child.getWidth()/2.0,
						parent.getCenterY() - (parent.getWidth() + child.getWidth() - child.getHeight())/2.0 - child.getHeight());
			else
				child.reOrientLocation(parent.getCenterX() - child.getWidth()/2.0,
						parent.getCenterY() - parent.getWidth()/2.0 - child.getHeight());
			break;
		case 3:
			if (child.child(parent.getOrientation()) == 0 ||
					child.child(parent.getOrientation()) == 2)
				child.reOrientLocation(parent.getCenterX() - parent.getHeight()/2.0 - child.getWidth(),
						parent.getCenterY() - child.getHeight()/2.0);
			else
				child.reOrientLocation(parent.getCenterX() - (parent.getHeight() + child.getWidth() - child.getHeight())/2.0 - child.getWidth(),
						parent.getCenterY() - child.getHeight()/2.0);
			break;
		default:
			System.err.println("Error!");
		}
	}

	@Override
	public double getHeight() {
		return _root.getHeight();
	}

	@Override
	public double getWidth() {
		return _root.getWidth();
	}

	@Override
	public double getX() {
		return _root.getX();
	}

	@Override
	public double getY() {
		return _root.getY();
	}

	public void setLocation(double x, double y) {
		_root.setLocation(x, y);
		TheCard[] children = _root.getChildren();
		if (children != null) {
			for (int i = 0; i < children.length; i++) {
				if (children[i] != null && !(children[i] instanceof TheBlankCard))
					restoreBranch(_root, children[i]);
			}
		}
	}

	@Override
	public void setSize(double w, double h) {}

	@Override
	public boolean containsClick(Point p) {
		if (_active) {
			boolean contains = false;
			for (int i = 0; i < _cards.size(); i++) {
				if(_cards.get(i).containsClick(p)) {
					_cards.get(i).clickAction();
					_dragged = _cards.get(i);
					outPoints(_dragged);
					contains = true;
				}
				else
					_cards.get(i).outAction();
			}
			return contains;
		}
		else
			return false;
	}

	public void upAction() {
		if (_active) {
			for (int i = 0; i < _cards.size(); i++) {
				_cards.get(i).upAction();
			}
			if (_dragged != null && _dragged != _root) {
				if (_dragged.reOriented()) {
					TheCard parent = _dragged.getParent();
					parent.removeChild(parent.childOrientation(parent.findChild(_dragged)));
					_minPoint.card().addChild(_dragged, _minPoint.orientation());
					_dragged.setParent(_minPoint.card());
					reOrient(_minPoint.card(), _dragged, _minPoint.orientation());
					finalizeBranch(_dragged);
					_board.getCommunicator().sendTreeTransferOrder(_dragged.getCard(), _minPoint.card().getCard(), _minPoint.orientation());
				}
				else
					restoreBranch(_dragged.getParent(), _dragged);
			}
			_dragged = null;
		}
	}

	@Override
	public boolean containsUp(Point p) {
		if (_active) {
			for (int i = 0; i < _cards.size(); i++) {
				_cards.get(i).containsUp(p);
			}
			upAction();
			return true;
		}
		return false;
	}

	@Override
	public boolean containsDrag(Point p) {
		if (_active) {
			boolean contains = false;
			if (_dragged != null)
				revertBranch(_dragged);
			for (int i = 0; i < _cards.size(); i++) {
				if(_cards.get(i).containsDrag(p)) {
					contains = true;
					_cards.get(i).showShadow();
				}
				if(_cards.get(i) != _root)
					_cards.get(i).dragAction();
			}
			//		Point2D.Double in = _dragged.getInPoint();
			Point in = p;
			if (_outPoints != null && _outPoints.size() > 0) {
				while(true) {
					_minPoint = _outPoints.get(0);
					double min = in.distance(_minPoint.point());
					for (int i = 1; i < _outPoints.size(); i++) {
						if (in.distance(_outPoints.get(i).point()) < min) {
							_minPoint = _outPoints.get(i);
							min = in.distance(_minPoint.point());
						}
					}
					if (min < 30) {
						reOrientBranch(_dragged, _minPoint.card(), _minPoint.orientation());
						if (!validBranch(_dragged)) {
							revertBranch(_dragged);
							_outPoints.remove(_minPoint);
						}
						else
							break;
					}
					else
						break;
				}
			}
			return contains;
		}
		return false;
	}

	@Override
	public double getCenterX() {
		return _root.getCenterX();
	}

	@Override
	public double getCenterY() {
		return _root.getCenterY();
	}

	private void outPoints(TheCard card) {
		Vector<TheCard> subtree = subtree(card, new Vector<TheCard>());
		_outPoints = new Vector<ThePointWrapper>();
		for (int i = 0; i < _cards.size(); i++) {
			if (!subtree.contains(_cards.get(i)))
				_outPoints.addAll(_cards.get(i).getOutPoints());
		}
	}

	private void reOrientBranch(TheCard card, TheCard parent, TheCardOrientation orientation) {
		reOrientTemp(parent, card, orientation);
		TheCard[] children = card.getChildren();
		TheCardOrientation[] orientations = new TheCardOrientation[] {TheCardOrientation.SOUTH,TheCardOrientation.EAST,TheCardOrientation.NORTH,TheCardOrientation.WEST};
		for (int i = 0; i < children.length; i++) {
			if (children[i] != null && !(children[i] instanceof TheBlankCard)) {
				reOrientBranch(children[i], card, orientations[i]);
			}
		}
	}

	public boolean transferable(TheCard parent) {
		if (!parent.getCard().getUsed() && parent.getCard().getTransferablePower() > 0)
			return true;
		TheCard[] children = parent.getChildren();
		for (int i = 0; i < children.length; i++) {
			if (children[i] != null && !(children[i] instanceof TheBlankCard)) {
				if (transferable(children[i]))
					return true;
			}
		}
		return false;
	}

	private void revertBranch(TheCard card) {
		card.revertReOrient();
		TheCard[] children = card.getChildren();
		if (children != null) {
			for (int i = 0; i < children.length; i++) {
				if (children[i] != null)
					revertBranch(children[i]);
			}
		}
	}

	private void restoreBranch(TheCard parent, TheCard card) {
		reOrient(parent, card, parent.childOrientation(parent.findChild(card)));
		TheCard[] children = card.getChildren();
		if (children != null) {
			for (int i = 0; i < children.length; i++) {
				if (children[i] != null && !(children[i] instanceof TheBlankCard))
					restoreBranch(card, children[i]);
			}
		}
	}

	private void finalizeBranch(TheCard card) {
		card.finalizeReOrient();
		TheCard[] children = card.getChildren();
		if (children != null) {
			for (int i = 0; i < children.length; i++) {
				if (children[i] != null)
					finalizeBranch(children[i]);
			}
		}
	}

	private boolean validBranch(TheCard card) {
		for (int i = 0; i < _cards.size(); i++) {
			if (_cards.get(i) != card) {
				if (_cards.get(i).getRotatedBox().intersects(card.getRotatedBox()))
					return false;
			}
		}
		TheCard[] children = card.getChildren();
		if (children != null) {
			for (int i = 0; i < children.length; i++) {
				if (children[i] != null) {
					if (!validBranch(children[i]))
						return false;
				}
			}
		}
		return true;
	}

	private Vector<TheCard> subtree(TheCard card, Vector<TheCard> v) {
		v.add(card);
		TheCard[] children = card.getChildren();
		if (children != null) {
			for (int i = 0; i < children.length; i++) {
				if (children[i] != null) {
					subtree(children[i],v);
				}
			}
		}
		return v;
	}

	public void resize() {
		_root.setLocation((_board.getWidth() - _root.getWidth())/2.0,
				(_board.getHeight() - _root.getHeight())/2.0);
		TheCard[] children = _root.getChildren();
		if (children != null) {
			for (int i = 0; i < children.length; i++) {
				if (children[i] != null && !(children[i] instanceof TheBlankCard))
					restoreBranch(_root, children[i]);
			}
		}
	}

	public void shift(double x, double y) {
		_root.setLocation(x + _root.getX(), y + _root.getY());
		TheCard[] children = _root.getChildren();
		if (children != null) {
			for (int i = 0; i < children.length; i++) {
				if (children[i] != null && !(children[i] instanceof TheBlankCard))
					restoreBranch(_root, children[i]);
			}
		}
	}
	
	public void shift(double rad) {
		_rotation += rad;
	}

	public void move(double rad) {
		_rotation = rad;
	}

	public void move(double x, double y) {
		_root.setLocation(x + (_board.getWidth() - _root.getWidth())/2.0,
				y + (_board.getHeight() - _root.getHeight())/2.0);
		TheCard[] children = _root.getChildren();
		if (children != null) {
			for (int i = 0; i < children.length; i++) {
				if (children[i] != null && !(children[i] instanceof TheBlankCard))
					restoreBranch(_root, children[i]);
			}
		}
	}

	public boolean active() {
		return _active;
	}


	public void setActive(boolean active) {
		_active = active;
	}
}
